public class FloatChromo extends Chromo
{
  private double[] valueVector;
  
  public FloatChromo()
  {
    valueVector = new double[Parameters.numGenes];
    
    // Initialize to random values that satisfy problem constraints.
    for (int i = 1; i < valueVector.length; i += 2)
    {
      valueVector[i - 1] = (Search.r.nextDouble() * 10.9 + 0.1); // [0.1, 10]
      if (valueVector[i - 1] > 10)
        valueVector[i - 1] = (int) valueVector[i - 1];
      
      valueVector[i] = 1.0 / valueVector[i - 1]; // starting at 0.1 avoids division by 0.
    }
    
    if (valueVector.length % 2 == 1) // odd length
    {
      valueVector[valueVector.length - 1] = 0.75;
    }
    else
    {
      int randomIndex = (int)(Search.r.nextDouble() * valueVector.length);
      valueVector[randomIndex] = 0.75 * valueVector[randomIndex]; // Not sure what this is for.
    }
    
    this.rawFitness = -1;   //  Fitness not yet evaluated
    this.sclFitness = -1;   //  Fitness not yet scaled
    this.proFitness = -1;   //  Fitness not yet proportionalized
  }
  
  public boolean isFeasibleSolution()
  {
    double constraintValue1 = getValueVector()[0];
    double constraintValue2 = getValueVector()[0];
    
    // First, check constraints
    for (int i = 1; i < Parameters.numGenes; i++)
    {
      constraintValue1 *= getValueVector()[i];
      constraintValue2 += getValueVector()[i];
    }
    
    return (constraintValue1 >= 0.75 && constraintValue2 <= 7.5 * Parameters.numGenes);
  }
  
  public double [] getValueVector()
  {
    return valueVector;
  }
  
  //  Mutate a Chromosome Based on Mutation Type *****************************
  
  public void doMutation()
  {
    //String mutChromo = "";
    //char x;
    
    switch (Parameters.mutationType)
    {
      case 1:     //  Replace with new random number
        for (int i = 0; i < 5; i++)
      {
        randnum = Search.r.nextDouble();
        
        if (randnum < Parameters.mutationRate)
          valueVector[i] = Search.r.nextDouble() * 2E10 - 1E10;
      }
        break;
      case 2:     // Dynamic mutation
        for (int i = 0; i < 5; i++)
      {
        randnum = Search.r.nextDouble();
        
        if (randnum < Parameters.mutationRate)
        {
          if (Search.r.nextDouble() < 0.5)
          {
           // System.out.println("UP BY: " + ((1E10 - valueVector[i])
           //   * (1 - Math.pow(Search.r.nextDouble(), Math.pow(1 - (Search.G + 1) / Parameters.generations, 5)))));
            // Will increase
            valueVector[i] += (1E10 - valueVector[i])
              * (1 - Math.pow(Search.r.nextDouble(), Math.pow(1 - (Search.G + 1) / Parameters.generations, 5)));
          }
          else
          {
            //System.out.println("DOWN BY: " + ((valueVector[i] - (-1E10))
            //  * (1 - Math.pow(Search.r.nextDouble(), Math.pow(1 - (Search.G + 1) / Parameters.generations, 5)))));
            // Will decrease
            valueVector[i] -= (valueVector[i] - (-1E10))
              * (1 - Math.pow(Search.r.nextDouble(), Math.pow(1 - (Search.G + 1) / Parameters.generations, 5)));
            
          }
        }
      }
        break;
   case 3: // Problem-specific mutation for geometrical crossover
  //for (int i = 0; i < 5; i++)
  //{
  // randnum = Search.r.nextDouble();
        //
  // if (randnum < Parameters.mutationRate)
  //  valueVector[i] = Search.r.nextDouble() * 2E10 - 1E10;
  //}
  
   for(int i=0; i<Parameters.numGenes; i++)
   {
    randnum = Search.r.nextDouble();
    if(randnum < Parameters.mutationRate) // if the element is chosen for mutation
    {
//     int j =  Search.r.nextInt(Parameters.numGenes);  // determine random j, 1 <= j <= N, j =/= i (Actually, indexing goes from 0 to N-1).
//     while(j!=i)
//     {
//      j =  Search.r.nextInt(Parameters.numGenes);
//     }
//     double q;     // select q such that 0.1 <= xi*q <= 10.0 and 0.1 <= xj/q <= 10.0
//     q = (Search.r.nextDouble() * 10.0 + 0.00001); // Not sure what range is appropriate for q, but this seems sufficient. (Note by Anthony)
//     while(!(0.1 <= valueVector[i]*q  && valueVector[i]*q <= 10.0) || !(0.1 <= valueVector[j]/q && valueVector[j]/q <= 10.0))  // THIS SECTION NEEDS CHECKING
//     {
//      q = (Search.r.nextDouble() * 10.0 + 0.00001);
//     }
     // generate a random j, j member of {0:N|i}
     int j = Search.r.nextInt(Parameters.numGenes-1);
     if (j == i)
      j++;
     
     //the following code is close, but not quite there, there's the issue with the lowerbound not being
     //attainable, since nextDouble will never return exactly 1.0
     double q;
     double upperbound = Math.min(10.0/valueVector[i], 10.0*valueVector[j]);
     double lowerbound = Math.max(0.1/valueVector[i], valueVector[j]/10.0);
     if (upperbound < lowerbound)
      System.out.println("Q inversion in mutation algorithm!");
     q = upperbound - Search.r.nextDouble()*(upperbound-lowerbound);
     
     //boolean previouslyFeasible = isFeasibleSolution();
     valueVector[i] = valueVector[i] * q; // xi = xi*q
     valueVector[j] = valueVector[j] / q; // xj = xj/q
     //if (previouslyFeasible && !isFeasibleSolution())
       //System.out.println("CONSTRAINTS NOT SATISFIED");
    }
   }
   break;
      default:
        System.out.println("FloatChromo ERROR - No mutation method selected");
    }
  }
  
  /*******************************************************************************
    *                             STATIC METHODS                                   *
    *******************************************************************************/
  

  
  //  Produce a new child from two parents  **********************************
  
  public void mateParents(int pnum1, int pnum2, Chromo parent2, Chromo child1, Chromo child2)
  {
    Chromo parent1 = this;
    int xoverPoint1;
    int xoverPoint2;
    
    switch (Parameters.xoverType){
      
      case 1:     //  Single Point Crossover
        
        //  Select crossover point
        // 4 possible crossover points
        xoverPoint1 = 1 + (int)(Search.r.nextDouble() * 4);
        
        //  Create child chromosome from parental material
        for (int i = 0; i < xoverPoint1; i++)
        {
          ((FloatChromo)child1).getValueVector()[i] = ((FloatChromo)parent1).getValueVector()[i];
          ((FloatChromo)child2).getValueVector()[i] = ((FloatChromo)parent2).getValueVector()[i];
        }
        
        for (int i = xoverPoint1; i < 5; i++)
        {
          ((FloatChromo)child1).getValueVector()[i] = ((FloatChromo)parent2).getValueVector()[i];
          ((FloatChromo)child2).getValueVector()[i] = ((FloatChromo)parent1).getValueVector()[i];
        }
        break;
        
      case 2:     //  Uniform arithmetic crossover
        double average;
        for (int i = 0; i < 5; i++)
      {
        if (Search.r.nextDouble() < Parameters.uniformXoverRate)
        {
          average = (((FloatChromo)parent1).getValueVector()[i] + ((FloatChromo)parent2).getValueVector()[i]) / 2.0;
          ((FloatChromo)child1).getValueVector()[i] = average;
          ((FloatChromo)child2).getValueVector()[i] = average;
        }
      }
        break;
      case 3:     //  Uniform crossover
        
      default:
        System.out.println("ERROR - Bad crossover method selected");
    }
    
    //  Set fitness values back to zero
    child1.rawFitness = -1;   //  Fitness not yet evaluated
    child1.sclFitness = -1;   //  Fitness not yet scaled
    child1.proFitness = -1;   //  Fitness not yet proportionalized
    child2.rawFitness = -1;   //  Fitness not yet evaluated
    child2.sclFitness = -1;   //  Fitness not yet scaled
    child2.proFitness = -1;   //  Fitness not yet proportionalized
  }
  
  //  Produce a new child from a single parent  ******************************
  
  public void mateParents(int pnum, Chromo child)
  {
    //  Create child chromosome from parental material
    for (int i = 0; i < Parameters.numGenes; i++)
      ((FloatChromo)child).getValueVector()[i] = getValueVector()[i];
    
    //  Set fitness values back to zero
    child.rawFitness = -1;   //  Fitness not yet evaluated
    child.sclFitness = -1;   //  Fitness not yet scaled
    child.proFitness = -1;   //  Fitness not yet proportionalized
  }
  
  // The target becomes the source.
  public void copy(Chromo source)
  {
    for (int i = 0; i < Parameters.numGenes; i++)
      valueVector[i] = ((FloatChromo)source).getValueVector()[i];
    
    rawFitness = source.rawFitness;
    sclFitness = source.sclFitness;
    proFitness = source.proFitness;
  }

public static Chromo mateParents(FloatChromo[] parents) {
 /* boolean previouslyFeasible = true;
  for (int i = 0; i < parents.length && previouslyFeasible; i++)
    previouslyFeasible = parents[i].isFeasibleSolution();*/
  
 double product;
 FloatChromo child = new FloatChromo();
 for (int i = 0; i < Parameters.numGenes; i++) {
  product = 1;
  for (int j = 0; j < parents.length; j++) {
   product *= parents[j].valueVector[i];
  }
  child.valueVector[i] = Math.pow(product, 1.0/Parameters.crossoverParents);
 }
 
 //if (previouslyFeasible && !child.isFeasibleSolution())
   //System.out.println("CONSTRAINTS NOT SATISFIED");
 return child;
}

public String toString()
{
  StringBuilder strBuilder = new StringBuilder();
  strBuilder.append("   RawFitness: " + rawFitness + "     <");
  
  for (int i = 0; i < Parameters.numGenes - 1; i++)
    strBuilder.append("" + getValueVector()[i] + ", ");
  
  strBuilder.append("" + getValueVector()[Parameters.numGenes - 1] + ">\n\n");
  
  return strBuilder.toString();
}

}
